﻿using System;
using System.IO;

namespace XnaWavPack
{
	public class WavPackDecoder : IDisposable
	{
		public int SampleRate { get; private set; }
		public int SampleCount { get; private set; }

		public int CurrentPosition { get; private set; }

		public int Channels { get; private set; }

		public WavPackDecoder(string filePath)
		{
			fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
			binaryReader = new BinaryReader(fileStream);
			wavPackContext = WavPackUtils.WavpackOpenFileInput(binaryReader);

			SampleRate = (int)WavPackUtils.WavpackGetSampleRate(wavPackContext);
			SampleCount = (int)WavPackUtils.WavpackGetNumSamples(wavPackContext);

			CurrentPosition = 0;

			Channels = WavPackUtils.WavpackGetReducedChannels(wavPackContext);
		}

		~WavPackDecoder()
		{
			Dispose(false);
		}

		public int GetSamples(byte[] sampleBuffer, int bufferOffset, int samplesRequested)
		{
			int samplesReceived = 0;

			if (samplesRequested * Channels > INTERNAL_SAMPLE_BUFFER_LENGTH)
			{
				while (samplesRequested > 0)
				{
					int samplesRequestedThisLoop = Math.Min(INTERNAL_SAMPLE_BUFFER_LENGTH / Channels, samplesRequested);
					int samplesReceivedThisLoop = GetSamples(sampleBuffer, samplesReceived * 2 * Channels, samplesRequestedThisLoop);

					samplesReceived += samplesReceivedThisLoop;
					CurrentPosition += samplesReceivedThisLoop;
					samplesRequested -= samplesReceivedThisLoop;

					if (samplesReceivedThisLoop < samplesRequestedThisLoop)
						samplesRequested = 0;
				}

				return samplesReceived;
			}
			else
			{
				samplesReceived = (int)WavPackUtils.WavpackUnpackSamples(wavPackContext, internalSampleBuffer, samplesRequested);
				CurrentPosition += samplesReceived;

				for (int i = 0; i < samplesReceived * Channels; i++)
				{
					if (BitConverter.IsLittleEndian)
					{
						sampleBuffer[i * 2 + 0 + bufferOffset] = (byte)(internalSampleBuffer[i] & 0xff);
						sampleBuffer[i * 2 + 1 + bufferOffset] = (byte)((internalSampleBuffer[i] >> 8) & 0xff);
					}
					else
					{
						sampleBuffer[i * 2 + 0 + bufferOffset] = (byte)((internalSampleBuffer[i] >> 8) & 0xff);
						sampleBuffer[i * 2 + 1 + bufferOffset] = (byte)(internalSampleBuffer[i] & 0xff);
					}
				}

				return samplesReceived;
			}
		}

		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		protected virtual void Dispose(bool disposing)
		{
			if (!disposed)
			{
				if (disposing)
				{
					// dispose of managed resources
					binaryReader.Dispose();
					fileStream.Dispose();
				}

				// dispose of unmanaged resources

				disposed = true;
			}
		}

		bool disposed = false;

		FileStream fileStream;
		BinaryReader binaryReader;
		WavpackContext wavPackContext;

		int[] internalSampleBuffer = new int[INTERNAL_SAMPLE_BUFFER_LENGTH];

		const int INTERNAL_SAMPLE_BUFFER_LENGTH = 2048;
	}
}
